package melody

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
)

type envelope struct {
	t   int
	msg []byte
}
type Session struct {
	// Request     *http.Request
	conn        *websocket.Conn
	output      chan *envelope
	melody      *Melody
	open        bool
	sessionImpl interface{}
	outputDone  chan struct{}
	sync.RWMutex
}

func (s *Session) Handler() interface{} {
	return s.sessionImpl
}
func (s *Session) registerimpl(h interface{}) {
	s.sessionImpl = h
}
func (s *Session) writeMessage(message *envelope) {
	if s.closed() {
		s.melody.WS.Error(s.sessionImpl, ErrClosed)
		return
	}
	select {
	case s.output <- message:
	default:
		s.melody.WS.Error(s.sessionImpl, ErrMessageBufferFull)
	}
}

func (s *Session) writeRaw(message *envelope) error {
	if s.closed() {
		return ErrWriteClosed
	}
	s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait))
	err := s.conn.WriteMessage(message.t, message.msg)
	if err != nil {
		return err
	}
	return nil
}

func (s *Session) closed() bool {
	s.RLock()
	defer s.RUnlock()
	return !s.open
}

func (s *Session) close() {
	s.Lock()
	open := s.open
	s.open = false
	s.Unlock()
	if open {
		s.conn.Close()
		close(s.outputDone)
	}
}
func (s *Session) ping() {
	s.writeRaw(&envelope{t: websocket.PingMessage, msg: []byte{}})
}
func (s *Session) writePump() {
	ticker := time.NewTicker(s.melody.Config.PingPeriod)
	defer ticker.Stop()
loop:
	for {
		select {
		case msg := <-s.output:
			err := s.writeRaw(msg)
			if err != nil {
				s.melody.WS.Error(s.sessionImpl, err)
				break loop
			}
			if msg.t == websocket.CloseMessage {
				break loop
			}
			if msg.t == websocket.TextMessage {
				s.melody.WS.HandlerSend(s.sessionImpl, msg.msg)
			}
			if msg.t == websocket.BinaryMessage {
				s.melody.WS.HandlerSend(s.sessionImpl, msg.msg)
			}
		case <-ticker.C:
			s.ping()
		case _, ok := <-s.outputDone:
			if !ok {
				break loop
			}
		}
	}
	s.close()
}

func (s *Session) readPump() {
	s.conn.SetReadLimit(s.melody.Config.MaxMessageSize)
	s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))
	s.conn.SetPongHandler(func(string) error {
		s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))
		s.melody.WS.HandlerPong(s.sessionImpl)
		return nil
	})
	s.conn.SetCloseHandler(func(code int, text string) error {
		return s.melody.WS.Close(s.sessionImpl, code, text)
	})
	for {
		t, message, err := s.conn.ReadMessage()
		if err != nil {
			s.melody.WS.Error(s.sessionImpl, err)
			break
		}
		if t == websocket.TextMessage {
			s.melody.WS.HandlerMessage(s.sessionImpl, message)
		}

		if t == websocket.BinaryMessage {
			s.melody.WS.HandlerMessage(s, message)
		}
	}
}
func (s *Session) Write(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}
	s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg})
	return nil
}
func (s *Session) WriteBinary(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}
	s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg})
	return nil
}
func (s *Session) Close() error {
	if s.closed() {
		return ErrSessionClosed
	}
	s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}})
	return nil
}
func (s *Session) CloseWithMsg(msg []byte) error {
	if s.closed() {
		return ErrSessionClosed
	}
	s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg})
	return nil
}
func (s *Session) IsClosed() bool {
	return s.closed()
}
